﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using OpenPhotoAlbum.DbAccess;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Data.Linq;

namespace OpenPhotoAlbum.BusinessLogics
{
    public class PhotoManager
    {
        private const int MAX_WIDTH = 640;
        private const int MAX_HEIGHT = 480;
        private const int TH_MAX_WIDTH = 200;
        private const int TH_MAX_HEIGHT = 150;
		public const int SMALL_TH_MAX_WIDTH = 120;
		public const int SMALL_TH_MAX_HEIGHT = 90;
		private const int AC_MAX_WIDTH = 160;
		private const int AC_MAX_HEIGHT = 120;
        public const string CONTENT_TYPE_JPEG = "image/jpeg";
		public const string CONTENT_TYPE_PNG = "image/png";

		public const int ICON_SIDE_LENGTH = 48;

        private static readonly ImageCodecInfo imageCodecInfo = null;
        private static readonly EncoderParameters encoderParameters = null;
		private static readonly EncoderParameters encoderParametersForUserIcon = null;
        public const long JPEG_QUALITY = 75;
		public const long USER_ICON_JPEG_QUALITY = 95;

        static PhotoManager()
        {
            // Initialize jpegCodecInfo.
            //
            ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders();
            foreach (ImageCodecInfo encoder in encoders)
            {
                if (encoder.MimeType.Equals(CONTENT_TYPE_JPEG, StringComparison.InvariantCultureIgnoreCase))
                {
                    imageCodecInfo = encoder;
                    break;
                }
            }
            if (imageCodecInfo == null) { throw new EncoderNotFoundException(); }

            // Initialize encoderParameters.
            //
            encoderParameters = new EncoderParameters(1);
            encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, JPEG_QUALITY);

			// Initialize encoderParametersForUserIcon.
			//
			encoderParametersForUserIcon = new EncoderParameters(1);
			encoderParametersForUserIcon.Param[0] = new EncoderParameter(Encoder.Quality, USER_ICON_JPEG_QUALITY);
        }

        #region Singleton

        private static readonly PhotoManager _instance = new PhotoManager();

        private PhotoManager() { }

        public static PhotoManager Instance
        {
            get { return _instance; }
        }

        #endregion

		public byte[] ResizePhotoToByteArray(byte[] photoData, int maxWidth, int maxHeight)
		{
			Image image = ResizePhoto(photoData, maxWidth, maxHeight);
			
			MemoryStream ms = new MemoryStream();
			image.Save(ms, imageCodecInfo, encoderParametersForUserIcon);
			byte[] result = ms.ToArray();
			
			ms.Close();
			image.Dispose();

			return result;
		}

        public Image ResizePhoto(byte[] photoData, int maxWidth, int maxHeight)
        {
            MemoryStream photoDataStream = new MemoryStream(photoData);
            Image image = Image.FromStream(photoDataStream);

            int originalWidth = image.Width;
            int originalHeight = image.Height;
            int targetWidth = originalWidth;
            int targetHeight = originalHeight;
            if (targetWidth > maxWidth)
            {
                double ratio = ((double)maxWidth) / targetWidth;
                targetWidth = (int)(targetWidth * ratio);
                targetHeight = (int)(targetHeight * ratio);
            }
            if (targetHeight > maxHeight)
            {
                double ratio = ((double)maxHeight) / targetHeight;
                targetWidth = (int)(targetWidth * ratio);
                targetHeight = (int)(targetHeight * ratio);
            }
            Image resizedImage = new Bitmap(targetWidth, targetHeight);
            Rectangle resizedRectangle = new Rectangle(0, 0, targetWidth, targetHeight);

            Graphics g = Graphics.FromImage(resizedImage);
            g.SmoothingMode = SmoothingMode.Default;
            g.CompositingQuality = CompositingQuality.Default;
            g.InterpolationMode = InterpolationMode.Default;
            g.DrawImage(image, resizedRectangle, 0, 0, originalWidth, originalHeight, GraphicsUnit.Pixel);

            g.Dispose();
            image.Dispose();
            photoDataStream.Close();

            return resizedImage;
        }

        public Guid SavePhoto(DbLinqClassesDataContext context, Guid albumId, string photoCaption, byte[] photoData)
        {
            ImageFormat imageFormat = ImageFormat.Jpeg;
            string contentType = CONTENT_TYPE_JPEG;

            Album album = AlbumManager.Instance.GetAlbum(context, albumId);
            if (album == null) { return Guid.Empty; }

            Image bigPhoto = ResizePhoto(photoData, MAX_WIDTH, MAX_HEIGHT);
            Image smallPhoto = ResizePhoto(photoData, TH_MAX_WIDTH, TH_MAX_HEIGHT);

            MemoryStream bigPhotoStream = new MemoryStream();
            bigPhoto.Save(bigPhotoStream, imageCodecInfo, encoderParameters);

            MemoryStream smallPhotoStream = new MemoryStream();
            smallPhoto.Save(smallPhotoStream, imageCodecInfo, encoderParameters);

            Photo photo = new Photo();
            photo.PhotoId = Guid.NewGuid();
			photo.PhotoTitle = photoCaption;
            photo.PhotoContentType = contentType;
            photo.PhotoData = new System.Data.Linq.Binary(bigPhotoStream.ToArray());
            photo.PhotoThumbnailContentType = contentType;
            photo.PhotoThumbnailData = new System.Data.Linq.Binary(smallPhotoStream.ToArray());
			photo.PhotoCreationDate = DateTime.UtcNow;

            album.Photos.Add(photo);
			album.AlbumModificationDate = DateTime.UtcNow;

            bigPhotoStream.Close();
            smallPhotoStream.Close();
            bigPhoto.Dispose();
            smallPhoto.Dispose();

            return photo.PhotoId;
        }

        public Guid SavePhoto(Guid albumId, string photoCaption, byte[] photoData)
        {
            DbLinqClassesDataContext context = new DbLinqClassesDataContext();
            Guid photoId = SavePhoto(context, albumId, photoCaption, photoData);
            context.SubmitChanges();

			ActionManager.Instance.AddPhotoUpload(photoId, photoCaption);
            
            return photoId;
        }

		public byte[] ProcessUserIcon(byte[] photoData)
		{
			MemoryStream photoDataStream = new MemoryStream(photoData);
			Image image = Image.FromStream(photoDataStream);

			Bitmap canvas = new Bitmap(ICON_SIDE_LENGTH, ICON_SIDE_LENGTH);
			
			Graphics gr = Graphics.FromImage(canvas);
			gr.SmoothingMode = SmoothingMode.HighQuality;
			gr.CompositingQuality = CompositingQuality.HighQuality;
			gr.InterpolationMode = InterpolationMode.HighQualityBicubic;

			Rectangle srcRect = new Rectangle();
			srcRect.Width = Math.Min(image.Width, image.Height);
			srcRect.Height = srcRect.Width;
			if (image.Width > image.Height)
			{
				srcRect.X = (image.Width - image.Height) / 2;
				srcRect.Y = 0;
			}
			else if (image.Width < image.Height)
			{
				srcRect.X = 0;
				srcRect.Y = (image.Height - image.Width) / 2;
			}
			else
			{
				srcRect.X = 0;
				srcRect.Y = 0;
			}

			Rectangle destRect = new Rectangle(0, 0, canvas.Width, canvas.Height);

			gr.DrawImage(image, destRect, srcRect, GraphicsUnit.Pixel);

			gr.Dispose();

			MemoryStream ms = new MemoryStream();
			canvas.Save(ms, imageCodecInfo, encoderParametersForUserIcon);
			byte[] result = ms.ToArray();
			ms.Close();

			canvas.Dispose();
			image.Dispose();
			photoDataStream.Close();

			return result;
		}

        public Photo GetPhoto(DbLinqClassesDataContext context, Guid photoId)
        {
            var photos = from p in context.Photos
                         where p.PhotoId.Equals(photoId)
                         select p;
            if (photos != null && photos.Count() > 0)
            {
                return photos.First();
            }
            return null;
        }

        public Photo GetPhoto(Guid photoId)
        {
            DbLinqClassesDataContext context = new DbLinqClassesDataContext();
            return GetPhoto(context, photoId);
        }

        public void DeletePhoto(Guid photoId)
        {
            DbLinqClassesDataContext context = new DbLinqClassesDataContext();
            
            Photo photo = GetPhoto(context, photoId);
            if (photo == null) { return; }

			CommentManager.Instance.DeleteComments(context, photo.Comments);
            context.Photos.DeleteOnSubmit(photo);
            context.SubmitChanges();
        }

        public void DeletePhotos(DbLinqClassesDataContext context, IEnumerable<Photo> photos)
        {
			foreach (DbAccess.Photo photo in photos)
			{
				CommentManager.Instance.DeleteComments(context, photo.Comments);
			}
            context.Photos.DeleteAllOnSubmit(photos);
        }

		public IEnumerable<Photo> GetPhotos(Guid albumId)
		{
			DbLinqClassesDataContext context = new DbLinqClassesDataContext();
			return GetPhotos(context, albumId);
		}

        public IEnumerable<Photo> GetPhotos(DbLinqClassesDataContext context, Guid albumId)
        {
			var results = from photo in context.Photos
						  where photo.AlbumId.Equals(albumId)
						  orderby photo.PhotoCreationDate ascending
						  select photo;
			return results;
        }

		public IEnumerable<Photo> GetPhotos(Guid albumId, int pageNumber, int pageSize)
		{
			DbLinqClassesDataContext context = new DbLinqClassesDataContext();
			return GetPhotos(context, albumId, pageNumber, pageSize);
		}

        public IEnumerable<Photo> GetPhotos(DbLinqClassesDataContext context, Guid albumId, int pageNumber, int pageSize)
        {
			return GetPhotos(context, albumId).Skip(pageNumber * pageSize).Take(pageSize);
        }

		public void UpdatePhotoTitle(Guid photoId, string title)
		{
			DbLinqClassesDataContext context = new DbLinqClassesDataContext();
			Photo photo = GetPhoto(context, photoId);
			photo.PhotoTitle = title;
			context.SubmitChanges();

			ActionManager.Instance.AddPhotoTitleChange(photoId, title);
		}

		public void UpdatePhotoDescription(Guid photoId, string description)
		{
			DbLinqClassesDataContext context = new DbLinqClassesDataContext();
			Photo photo = GetPhoto(context, photoId);
			photo.PhotoDescription = description;
			context.SubmitChanges();

			ActionManager.Instance.AddPhotoDescriptionChange(photoId, description);
		}

		public void SaveVisualEffect(Guid photoId, string veName)
		{
			Controls.PhotoProcessors.IPhotoProcessor processor = Controls.PhotoProcessors.ProcessorFactory.CreateProcessor(veName);

			Photo photo = GetPhoto(photoId);
			byte[] photoData = processor.Process(photo.PhotoData.ToArray());

			Image bigPhoto = ResizePhoto(photoData, MAX_WIDTH, MAX_HEIGHT);
			Image smallPhoto = ResizePhoto(photoData, TH_MAX_WIDTH, TH_MAX_HEIGHT);

			MemoryStream bigPhotoStream = new MemoryStream();
			bigPhoto.Save(bigPhotoStream, imageCodecInfo, encoderParameters);

			MemoryStream smallPhotoStream = new MemoryStream();
			smallPhoto.Save(smallPhotoStream, imageCodecInfo, encoderParameters);

			DbLinqClassesDataContext context = new DbLinqClassesDataContext();
			Photo result = GetPhoto(context, photoId);
			result.PhotoData = new Binary(bigPhotoStream.ToArray());
			result.PhotoThumbnailData = new Binary(smallPhotoStream.ToArray());
			context.SubmitChanges();
		}

		public void SaveVisualEffectAs(Guid photoId, string veName)
		{
			Controls.PhotoProcessors.IPhotoProcessor processor = Controls.PhotoProcessors.ProcessorFactory.CreateProcessor(veName);

			Photo photo = GetPhoto(photoId);
			byte[] photoData = processor.Process(photo.PhotoData.ToArray());

			SavePhoto(photo.AlbumId, photo.PhotoTitle, photoData);
		}

		public Guid GetUserId(Guid photoId)
		{
			if (photoId == null || photoId.Equals(Guid.Empty)) { return Guid.Empty; }

			DbLinqClassesDataContext context = new DbLinqClassesDataContext();
			var results = from photo in context.Photos
						  where photo.PhotoId.Equals(photoId)
						  select photo.Album.UserId;

			if (results == null || results.Count() != 1) { return Guid.Empty; }

			return results.First();
		}

		public string GetUserName(Guid photoId)
		{
			if (photoId == null || photoId.Equals(Guid.Empty)) { return string.Empty; }

			DbLinqClassesDataContext context = new DbLinqClassesDataContext();
			var results = from photo in context.Photos
						  where photo.PhotoId.Equals(photoId)
						  select photo.Album.User.UserName;

			if (results == null || results.Count() != 1) { return string.Empty; }

			return results.First();
		}

		public User GetUser(Guid photoId)
		{
			if (photoId == null || photoId.Equals(Guid.Empty)) { return null; }

			DbLinqClassesDataContext context = new DbLinqClassesDataContext();
			var results = from photo in context.Photos
						  where photo.PhotoId.Equals(photoId)
						  select photo.Album.User;

			if (results == null || results.Count() != 1) { return null; }

			return results.Single();
		}

		public void SetAsCover(Guid photoId)
		{
			DbLinqClassesDataContext context = new DbLinqClassesDataContext();
			
			Photo photo = GetPhoto(context, photoId);
			if (photo == null || photo.IsAlbumCover) { return; }

			var photos = from p in context.Photos
						 where p.AlbumId.Equals(photo.AlbumId) && p.IsAlbumCover
						 select p;
			foreach (Photo p in photos) { p.IsAlbumCover = false; }

			photo.IsAlbumCover = true;

			context.SubmitChanges();
		}

		public IEnumerable<Photo> GetLatestUploadedPhotos(int count)
		{
			DbLinqClassesDataContext context = new DbLinqClassesDataContext();
			var results = from p in context.Photos
						  orderby p.PhotoCreationDate descending
						  select p;
			return results.Take(count);
		}

		public byte[] PaintAlbumCover(byte[] data)
		{
			Image bitmap = ResizePhoto(data, AC_MAX_WIDTH, AC_MAX_HEIGHT);

			int width = bitmap.Width;
			int height = bitmap.Height;

			Bitmap canvas = new Bitmap(width + 4, height + 4);

			Graphics gr = Graphics.FromImage(canvas);

			gr.FillRectangle(Brushes.White, 0, 0, width + 4, height + 4);
			gr.DrawRectangle(Pens.Silver, 4, 4, width - 1, height - 1);
			gr.DrawRectangle(Pens.Silver, 2, 2, width - 1, height - 1);
			gr.FillRectangle(Brushes.White, 3, 3, width - 2, height - 2);

			gr.DrawImage(bitmap, 0, 0, width, height);

			MemoryStream msOutput = new MemoryStream();
			canvas.Save(msOutput, imageCodecInfo, encoderParameters);
			byte[] result = msOutput.ToArray();

			msOutput.Close();
			gr.Dispose();
			canvas.Dispose();
			bitmap.Dispose();

			return result;
		}

		public void BanUnbanPhoto(Guid photoId)
		{
			DbLinqClassesDataContext context = new DbLinqClassesDataContext();
			Photo photo = GetPhoto(context, photoId);
			photo.IsBanned = !photo.IsBanned;
			context.SubmitChanges();
		}

		public void PromoteDemotePhoto(Guid photoId)
		{
			DbLinqClassesDataContext context = new DbLinqClassesDataContext();
			Photo photo = GetPhoto(context, photoId);
			photo.IsPromoted = !photo.IsPromoted;
			context.SubmitChanges();
		}

		public IEnumerable<Photo> GetPromotedPhotos(int count)
		{
			DbLinqClassesDataContext context = new DbLinqClassesDataContext();
			var results = (from p in context.Photos
						   where p.IsPromoted && !p.IsBanned
						   orderby p.PhotoCreationDate descending
						   select p).Take(count);
			return results;
		}
    }

    public class EncoderNotFoundException : Exception { }

	public class PhotoTypes
	{
		public const string FullSize = "f";
		public const string Thumbnail = "t";
		public const string SmallThumbnail = "s";
		public const string AlbumCover = "a";
		public const string PostPadCanvas = "p";
		public const string Captcha = "c";
		public const string UserIcon = "u";
	}
}
